package vn.vfriends.id.servlet;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.inject.Inject;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.openid4java.OpenIDException;
import org.openid4java.consumer.ConsumerManager;
import org.openid4java.consumer.VerificationResult;
import org.openid4java.discovery.DiscoveryInformation;
import org.openid4java.discovery.Identifier;
import org.openid4java.message.AuthRequest;
import org.openid4java.message.AuthSuccess;
import org.openid4java.message.ParameterList;
import org.openid4java.message.ax.AxMessage;
import org.openid4java.message.ax.FetchRequest;
import org.openid4java.message.ax.FetchResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import vn.vfriends.id.jpa.controller.GroupJpaController;
import vn.vfriends.id.jpa.controller.UserJpaController;
import vn.vfriends.id.jpa.entity.Group;
import vn.vfriends.id.jpa.entity.Role;
import vn.vfriends.id.jpa.entity.User;

/**
 *
 * @author tuan@vfriends.vn
 */
@WebServlet(name = "openIdServlet", urlPatterns = {"/openIdServlet"})
public class OpenIdServlet1 extends HttpServlet {

    private static Logger logger = LoggerFactory.getLogger(OpenIdServlet1.class);
    public ConsumerManager manager;
    final static public String YAHOO_ENDPOINT = "https://me.yahoo.com";
    final static public String GOOGLE_ENDPOINT = "https://www.google.com/accounts/o8/id";
    @Inject private UserJpaController userController;
    @Inject private GroupJpaController groupController;

    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        this.manager = new ConsumerManager();

    }
    
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Identifier identifier = this.verifyResponse(req);
        if (identifier != null) {
            resp.sendRedirect("home.vfs");
        } else {
            logger.error("Login with openid failed");
            resp.sendRedirect("login.vfs?error=failed");
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String identifier = "";
        if (req.getParameter("identifier") != null) {
            identifier = (String) req.getParameter("identifier");
        } else {
            identifier = (String) req.getAttribute("identifier");
        }
        this.authRequest(identifier, req, resp);
    }

    
    // --- placing the authentication request ---
    public String authRequest(String userSuppliedString, HttpServletRequest httpReq, HttpServletResponse httpResp) throws IOException {
        try {
            // configure the return_to URL where your application will receive
            // the authentication responses from the OpenID provider
            //String returnToUrl = "http://localhost:8080/id/home.vfs";
            String returnToUrl = httpReq.getRequestURL().toString();

            // --- Forward proxy setup (only if needed) ---
            // ProxyProperties proxyProps = new ProxyProperties();
            // proxyProps.setProxyName("proxy.example.com");
            // proxyProps.setProxyPort(8080);
            // HttpClientFactory.setProxyProperties(proxyProps);

            // perform discovery on the user-supplied identifier
            List discoveries = manager.discover(userSuppliedString);

            // attempt to associate with the OpenID provider
            // and retrieve one service endpoint for authentication
            DiscoveryInformation discovered = manager.associate(discoveries);

            // store the discovery information in the user's session
            httpReq.getSession().setAttribute("openid-disc", discovered);

            // obtain a AuthRequest message to be sent to the OpenID provider
            AuthRequest authReq = manager.authenticate(discovered, returnToUrl);

            FetchRequest fetch = FetchRequest.createFetchRequest();
            if (userSuppliedString.startsWith(GOOGLE_ENDPOINT)) {
                fetch.addAttribute("email", "http://axschema.org/contact/email", true);
                fetch.addAttribute("firstName", "http://axschema.org/namePerson/first", true);
                fetch.addAttribute("lastName", "http://axschema.org/namePerson/last", true);
            } else if (userSuppliedString.startsWith(YAHOO_ENDPOINT)) {
                fetch.addAttribute("email", "http://axschema.org/contact/email", true);
                fetch.addAttribute("fullname", "http://axschema.org/namePerson", true);
            } else { // works for myOpenID
                fetch.addAttribute("fullname", "http://schema.openid.net/namePerson", true);
                fetch.addAttribute("email", "http://schema.openid.net/contact/email", true);
            }

            // attach the extension to the authentication request
            authReq.addExtension(fetch);


            //if (!discovered.isVersion2()) {
            // Option 1: GET HTTP-redirect to the OpenID Provider endpoint
            // The only method supported in OpenID 1.x
            // redirect-URL usually limited ~2048 bytes
            httpResp.sendRedirect(authReq.getDestinationUrl(true));
            //return null;
//            } else {
//                // Option 2: HTML FORM Redirection (Allows payloads >2048 bytes)
//
//                RequestDispatcher dispatcher =
//                        getServletContext().getRequestDispatcher("formredirection.jsp");
//                httpReq.setAttribute("parameterMap", authReq.getParameterMap());
//                httpReq.setAttribute("destinationUrl", authReq.getDestinationUrl(false));
//                dispatcher.forward(httpReq, httpResp);
//            }
        } catch (OpenIDException e) {
            // present error to the user
            logger.error(e.getMessage(), e);
        }

        return null;
    }

    // --- processing the authentication response ---
    public Identifier verifyResponse(HttpServletRequest httpReq) {
        try {
            // extract the parameters from the authentication response
            // (which comes in as a HTTP request from the OpenID provider)
            ParameterList response = new ParameterList(httpReq.getParameterMap());

            // retrieve the previously stored discovery information
            DiscoveryInformation discovered = (DiscoveryInformation) httpReq.getSession().getAttribute("openid-disc");

            // extract the receiving URL from the HTTP request
            StringBuffer receivingURL = httpReq.getRequestURL();
            String queryString = httpReq.getQueryString();
            if (queryString != null && queryString.length() > 0) {
                receivingURL.append("?").append(httpReq.getQueryString());
            }

            // verify the response; ConsumerManager needs to be the same
            // (static) instance used to place the authentication request
            VerificationResult verification = manager.verify(receivingURL.toString(), response, discovered);

            // examine the verification result and extract the verified identifier
            Identifier verified = verification.getVerifiedId();
            if (verified != null) {
                AuthSuccess authSuccess =
                        (AuthSuccess) verification.getAuthResponse();

                if (authSuccess.hasExtension(AxMessage.OPENID_NS_AX)) {
                    FetchResponse fetchResp = (FetchResponse) authSuccess.getExtension(AxMessage.OPENID_NS_AX);

                    List emails = fetchResp.getAttributeValues("email");
                    String email = (String) emails.get(0);
                    
                    List fullNames = fetchResp.getAttributeValues("fullname");
                    String fullName = (String) fullNames.get(0);
                    
                    if(fullName == null || fullName.length() == 0) {
                        List firstNames = fetchResp.getAttributeValues("firstName");
                        String firstName = (String) firstNames.get(0);
                        
                        List lastNames = fetchResp.getAttributeValues("lastName");
                        String lastName = (String) lastNames.get(0);
                        
                        fullName = firstName + " " + lastName;
                    }
                    
                    // Check exist user
                    User user = userController.findByUsername(email);
                    if(user == null) {
                        user = new User();
                        user.setUsername(email);
                        user.setEmail(email);
                        user.setFullName(fullName);
                        user.setIsActivated(true);
                        user.setIsExternal(true);
                        
                        //  Group
                        Group group = this.groupController.findByCode(Group.GroupCode.CUSTOMER);
                        if (group != null) {
                            List<Group> groupList = new ArrayList<Group>();
                            groupList.add(group);
                            user.setGroups(groupList);
                        }
                        
                        userController.create(user);
                    }
                    HttpSession session = httpReq.getSession();
                    session.setAttribute("LOGGED_USER", user);
                    
                    // Get roles of logged user
                    List<Group> groupList = user.getGroups();
                    Set<Role> roleSet = new HashSet<Role>();
                    for (Group group : groupList) {
                        List<Role> roleList = group.getRoles();
                        for (Role role : roleList) {
                            roleSet.add(role);
                        }
                    }
                    session.setAttribute("LOGGED_USER_ROLES", roleSet);
                }

                return verified;  // success
            }
        } catch (Exception e) {
            // present error to the user
            logger.error(e.getMessage(), e);
        }

        return null;
    }
}
